﻿/* ***********************************************************************
Flash CS4 tutorial by Doug Ensley and Barbara Kaskosz

http://www.flashandmath.com/

Last modified: June 6, 2009
************************************************************************ */

package flashandmath.as3 {
	
	import flash.display.Sprite;
	import flash.display.Bitmap;
	import flash.display.BitmapData;	
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFieldType;
	import flash.filters.DropShadowFilter;
	
	import flash.geom.Point;
	import flash.geom.PerspectiveProjection;
	
	//We will use our custom class, ImageLoader to load bitmaps at runtime.
	
	import flashandmath.as3.ImageLoader;

	import fl.transitions.Tween;
	import fl.transitions.TweenEvent;
	import fl.transitions.easing.*;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	
	
    public  class TweenFlipCS4 extends Sprite {
		 
		private var turnSpeed:Number;
		
	    private var bdFirst:BitmapData;
		private var bdSecond:BitmapData;
		  
		private var imgsSrcs:Array;  
		private var imgLoader:ImageLoader;
		private var ErrorBox:TextField;
		
		private var picWidth:Number;
		private var picHeight:Number;
		
		private var holder:Sprite;  
		private var side0:Sprite;
  		private var side1:Sprite;
		
		private var sharpSide0Img:Bitmap;
  		private var sharpSide1Img:Bitmap;
		
		private var side0Img:Bitmap;
  		private var side1Img:Bitmap;
		
		private var whichSideNext:int;
		
		private var spinType:String;
		   		  
		/** 
		Variables for the generic Object that controls the tweens, the two tweens 
		(one for flipping front-to-back, one for flipping back-to-front), and the URL 
		to be navigated to on "mouse click."
		**/
		
		private var objTw:Object;
		private var tw0:Tween;
		private var tw1:Tween;
		private var thisURL:String;
		
		private var pp:PerspectiveProjection;
		
	  	/*
		The constructor of the TweenFlipCS4 class takes two String
        parameters representing the addresses of the image files
        for the front and the back of your card to be loaded by the class
        at runtime. The third optional String parameter tells the contructor
        if you want a vertical flip (the default) or the horizontal flip.
        In the latter case, you should enter, for example:

        card1=new TweenFlipCS4("bkimage.jpg","bkcard.jpg","horizontal");
		*/
		  
	    public function TweenFlipCS4(img0:String,img1:String,t:String="vertical"){
			
			imgsSrcs=[img0,img1];
			
		    imgLoader=new ImageLoader();
			
			//Messages to the user about loading will be displyed in ErrorBox.
		    
			ErrorBox=new TextField();
		    
			this.addChild(ErrorBox);
			
		 	setUpErrorBox();
			
		   	setErrorBoxSizeAndPos(250,150,0,0);
			
		    setErrorBoxFormat(0xCCCCFF,12);
			
			/* 
			turnSpeed is measured in seconds to complete the flip. 
			The default value is 1.5 seconds.
			*/
			
			turnSpeed=1.5;
			
			// The default URL is Flashandmath.com, of course!
			
			thisURL = "http://www.flashandmath.com";
			
			if(t=="vertical"){
				
				spinType="vertical";
				
			} else {spinType="horizontal";}
			
			prepTweens();
			
			prepImgs();
			
		}
		
		//End of constructor.
		
		public function setSpeed(t:Number):void {
			tw0.duration = t;
			tw1.duration = t;
		}
		
		public function setFunc(f:Function):void {
			tw0.func = f;
			tw1.func = f;
		}
	
		public function setURL(s:String):void {
			thisURL = s;
		}
		
		//The function InitApp is running after images are successfully loaded.
		
		private function initApp():void {
			
			ErrorBox.text="";
			
			ErrorBox.visible=false;
			
			bdFirst=imgLoader.bitmapsArray[0].bitmapData;
			
			bdSecond=imgLoader.bitmapsArray[1].bitmapData;
			
			side0Img=new Bitmap(bdFirst);
			
			side1Img=new Bitmap(bdSecond);
			
			//We create two copies of our images. See comment before twReset function
			//for explanation.
			
			sharpSide0Img=new Bitmap(bdFirst);
			
			sharpSide1Img=new Bitmap(bdSecond);
			
  			picWidth=side0Img.width;
			
		  	picHeight=side0Img.height;
			
			holder=new Sprite();
			
		  	this.addChild(holder);
			
			holder.x=picWidth/2;
			
			holder.y=picHeight/2;
			
		  	side0=new Sprite();
			
		  	holder.addChild(side0);
			
		  	side0Img.x=-picWidth/2;
			
		  	side0Img.y=-picHeight/2;
			
			side0.x=0;
			
			side0.y=0;
			
			side0.addChild(side0Img);
		
			side1=new Sprite();
			
		  	holder.addChild(side1);
			
		  	side1Img.x=-picWidth/2;
			
		  	side1Img.y=-picHeight/2;
			
			side1.x=0;
			
			side1.y=0;
			
			side1.addChild(side1Img);
			
			//In order to appear correctly after a flip, the back side has to be
			//rotated initially.
			
			if(spinType=="vertical"){
			
			side1.rotationY = 180; } else {
				
				side1.rotationX = 180;
				
			}
		  
		  	holder.filters = [ new DropShadowFilter() ];
			
			this.addChild(sharpSide0Img);
			
			this.addChild(sharpSide1Img);
			
			sharpSide0Img.visible=true;
			
			sharpSide1Img.visible=false;
			
			//Each instance of the TweenFlipCS4 class has its own PerspectiveProjection object.
			
			pp=new PerspectiveProjection();
			
			pp.fieldOfView=60;
			
			pp.projectionCenter=new Point(picWidth/2,picHeight/2);
			
			this.transform.perspectiveProjection=pp;
			
		  	renderView(0);	
			
		  	setUpListeners();
		}
	
		
		
		private function prepTweens():void {
			
			/** 
			The method of using a generic Object to control a Tween is explained in more detail in 
			the Intermediate area tutorial "Tween Tricks in ActionScript 3 and Flash CS3." Note that
			the default turnSpeed and easing function are overwritten by the setSpeed and setFunc 
			methods defined below.
			**/
			
			objTw = {t: 0};
			
			tw0 = new Tween(objTw,"t",Regular.easeOut,0,180,turnSpeed, true);
			
			tw0.stop();
			
			tw0.addEventListener(TweenEvent.MOTION_CHANGE, twRotate);
			
			tw0.addEventListener(TweenEvent.MOTION_FINISH, twReset);
			
			tw1 = new Tween(objTw,"t",Regular.easeOut,180,360,turnSpeed, true);
			
			tw1.stop();
			
			tw1.addEventListener(TweenEvent.MOTION_CHANGE, twRotate);
			
			tw1.addEventListener(TweenEvent.MOTION_FINISH, twReset);
			
			
			
		}
		
		/**
		Each time the tw0 or tw1 Tween fires we call the renderView function using objTw.t as its argument.
		**/
		
		private function twRotate(twevt:TweenEvent):void {
			
			renderView(objTw.t);
		}
		
		/**
		When the tw0 or tw1 Tween finishes playing the listeners for mouse over and mouse out are added once again.
		They were removed when ROLL_OVER or ROLL_OUT events occured. The last line in the function
		teReset places the Bitmap sharpSide0Img or sharpSide1Img on top of the card. We do this as the
		images side0Img and side1Img contained in a 3D object, holder, are blurred. (See the tutorial
		3D News Ticker in Flash CS4 and AS3, http://www.flashandmath.com/flashcs4/ticker/
		for a discussion of this effect.) Since a Bitmap is not an InteractiveDisplayObject,
		they do not interfere with sides of holder to repond to mouse events.
		**/
		
		private function twReset(twevt:TweenEvent):void {
			
			
			side0.addEventListener(MouseEvent.ROLL_OVER,side0Over);
			
			side0.addEventListener(MouseEvent.ROLL_OUT,side0Out);
			
			side1.addEventListener(MouseEvent.ROLL_OVER,side1Over);
			
			side1.addEventListener(MouseEvent.ROLL_OUT,side1Out);
			
			this["sharpSide"+String(whichSideNext)+"Img"].visible=true;
			
			
			
		}
		
				
	  	private function renderView(t:Number):void {
			
			if ( (t < 90) || (t > 270) ) {
				side0.visible = true;
				side1.visible = false;
			}
			else {
				side0.visible = false;
				side1.visible = true;
			}
			
			if(spinType=="vertical"){
			
			 holder.rotationY = t; } else {
				
				holder.rotationX = t;
				
			}
			
  		}

		/**
		When we set up listeners, we are now listening for mouse over, mouse out and click.  
		The latter will only be heard by side1, so it is important that the card is 
		constructed so that side1 is the side you wish to register the click for navigation.
		**/
		
		private function setUpListeners():void {
			
			imgLoader.removeEventListener(ImageLoader.LOAD_ERROR,errorLoading);
			
			imgLoader.removeEventListener(ImageLoader.IMGS_LOADED,allLoaded);
				
			
			side0.addEventListener(MouseEvent.ROLL_OVER,side0Over);
			
			side0.addEventListener(MouseEvent.ROLL_OUT,side0Out);
			
			side1.addEventListener(MouseEvent.ROLL_OVER,side1Over);
			
			side1.addEventListener(MouseEvent.ROLL_OUT,side1Out);
			
			side1.addEventListener(MouseEvent.CLICK,goThere);
			
		}
	
		/** 
		When side0 hears the mouse over or out event, the listeners are removed & tw0 starts.
		They will be added back when the Tween tw0 finishes playing.
		**/
		
		private function side0Over(e:MouseEvent):void {
			
			side0.removeEventListener(MouseEvent.ROLL_OVER,side0Over);
			
			side1.removeEventListener(MouseEvent.ROLL_OUT,side1Out);
			
			side0.removeEventListener(MouseEvent.ROLL_OUT,side0Out);
			
			side1.removeEventListener(MouseEvent.ROLL_OVER,side1Over);
			
			sharpSide0Img.visible=false;
			
			sharpSide1Img.visible=false;
			
			whichSideNext=1;
			
			tw0.rewind();
			
			tw0.start();
		}
		
		private function side0Out(e:MouseEvent):void {
			
			side0.removeEventListener(MouseEvent.ROLL_OVER,side0Over);
			
			side1.removeEventListener(MouseEvent.ROLL_OUT,side1Out);
			
			side0.removeEventListener(MouseEvent.ROLL_OUT,side0Out);
			
			side1.removeEventListener(MouseEvent.ROLL_OVER,side1Over);
			
			sharpSide0Img.visible=false;
			
			sharpSide1Img.visible=false;
			
			whichSideNext=1;
			
			tw0.rewind();
			
			tw0.start();
		}
		
		/** 
		When side1 hears the mouse over or out event, the listeners are removed and tw1 starts.
		They will be added back when the Tween tw1 finishes playing.
		**/
		
		private function side1Out(e:MouseEvent):void {
			
			side0.removeEventListener(MouseEvent.ROLL_OVER,side0Over);
			
			side1.removeEventListener(MouseEvent.ROLL_OUT,side1Out);
			
			side0.removeEventListener(MouseEvent.ROLL_OUT,side0Out);
			
			side1.removeEventListener(MouseEvent.ROLL_OVER,side1Over);
			
			sharpSide0Img.visible=false;
			
			sharpSide1Img.visible=false;
			
			whichSideNext=0;
			
			tw1.rewind();
			
			tw1.start();
		}
		
		private function side1Over(e:MouseEvent):void {
			
			side0.removeEventListener(MouseEvent.ROLL_OVER,side0Over);
			
			side1.removeEventListener(MouseEvent.ROLL_OUT,side1Out);
			
			side0.removeEventListener(MouseEvent.ROLL_OUT,side0Out);
			
			side1.removeEventListener(MouseEvent.ROLL_OVER,side1Over);
			
			sharpSide0Img.visible=false;
			
			sharpSide1Img.visible=false;
			
			whichSideNext=0;
			
			tw1.rewind();
			
			tw1.start();
		}
		
		// Manage simple navigation to thisURL when side1 is clicked.
		
		private function goThere(e:MouseEvent):void {
			
			navigateToURL(new URLRequest(thisURL), "_blank"  );
		}
		
		
		/*
		In the next part (which really runs first), an instance of ImageLoader is used to load images
		for the sides of the flipping card.
		*/
	
		private function prepImgs():void {
			
			imgLoader.addEventListener(ImageLoader.LOAD_ERROR,errorLoading);
			
		   	imgLoader.addEventListener(ImageLoader.IMGS_LOADED,allLoaded);
			
		   	imgLoader.loadImgs(imgsSrcs);
			
			ErrorBox.visible=true;
			
			ErrorBox.text="Loading images...";
		}
	
	    private function errorLoading(e:Event):void {
			
	       ErrorBox.visible=true;
		   
		   ErrorBox.text="There has been an error loading images. The server may be busy. Try refreshing the page.";
       	}
	
	   	private function allLoaded(e:Event):void {
			
			initApp();
			
	   	}

		/*
		The part of the code below, sets up the error text field in which
		messages to the user about loading are displayed.
		*/
		
		
		private function setUpErrorBox():void {
			
			ErrorBox.type=TextFieldType.DYNAMIC;
			ErrorBox.wordWrap=true;
			ErrorBox.border=false;
			ErrorBox.background=false;
			ErrorBox.text="";
			ErrorBox.visible=false;
			ErrorBox.mouseEnabled=false;
			
		}
		
		public function setErrorBoxFormat(colo:Number,s:Number): void {
			
			var errorFormat:TextFormat=new TextFormat();
			
			errorFormat.color=colo;
			errorFormat.size=s;
			errorFormat.font="Arial";
			ErrorBox.defaultTextFormat=errorFormat;
		}
		
		public function setErrorBoxSizeAndPos(w:Number,h:Number,a:Number,b:Number): void {
			
			ErrorBox.width=w;
			ErrorBox.height=h;
			ErrorBox.x=a;
			ErrorBox.y=b;
			
		}
	}
}